///----------------------------------------------------------------------------
/// File Name: ApplicationState.cs
/// 
/// Description: 
/// The MavAppState class holds static application configuration settings for 
/// objects requiring configuration information. MavAppState provides a one stop
/// shop for settings rather then have configuration functions in separate 
/// classes.
/// 
/// History:
/// 08 Mar 2004	Aaron Clauson	Created.
///
/// License:
/// Public Domain.
///----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
using BlueFace.Sys;
using MaverickLite.Security;
using log4net;

namespace MaverickLite.Config
{
	/// <summary>
	/// This class maintains static application configuration settings that can be used by all classes within
	/// the AppDomain. This class is the one stop shop for retrieving or accessing application configuration settings.
	/// </summary>
	public class MavAppState
	{
		private const string APP_CONFIG_SECTION = "MaverickLite";						// Name of the section in theApp.Config file storing dispatcher settings.
		private const string APP_CONFIG_KEY_NAME = "key";								// Name of attribute holding the name of an application setting key in the App.Config file.
		private const string APP_CONFIG_VALUE_NAME = "value";							// Name of attribute holding the name of an application setting value in the App.Config file.
		private const string INITPARAM_CONFIG_FILE = "configFile";						// Application configuration parameter key for the path to the MaverickLite config file.
		private const string DIRECTORY_PATH_KEY = "securityDirectoryPath";				// Application configuration parameter key for the path to the security directory.
		private const string APP_LOGGING_ID = "MaverickLite";							// Name of log4net identifier.
		private const string APP_VISITORLOG_ID = "VisitorLog";							// Name of log4net identifier for the visitor log.
		private const string INITPARAM_RELOAD_COMMAND = "reloadCommand";				// Pre-defined command that causes the Maverick config file to be reloaded.
		private const string INITPARAM_CURRENT_CONFIG_COMMAND = "currentConfigCommand";	// Pre-defined command that will output the Maverick config file.	
		private const string DEFAULT_CONFIG_FILE = "mavericklite.config";				// Default location of the xml configuration file relative to the current web application.
		//private const string VISITOR_LOG_FILE = "visitorLogFile";						// File path to store a visitor log.
		public const string APP_VIRTUALDIR_KEY = "virtualDir";							// The app config key that specifies the virtual directory of the web application.
		public const string WEB_DBCONNSTRING_KEY = "WebDBConnectionString";				// The database connection string.
		public const string SUBSCRIPTION_NOTIFICATIONADDRESS_KEY = "SubscriptionNotificationAddress";	// The email addess to send notifications to for new subscriptions.
		public const string RELOAD_COMMAND_KEY = "reloadcommand";						// The key for the reload command that causes the reloading of all the commands.
		public const string AUTHENTICATOR_DBCONN_STR = "authenticatorDBConnStr";		// The key for the default db authentication string, gets overridden by any key defined by a controller.
		public const string AUTHENTICATOR_DB_TYPE = "authenticatorDBType";				// The key for the authentication db type.
		public const string AUTHENTICATOR_DIR_PATH = "authenticationDirectoryPath";		// The key for the file based authentication directory path.
		public const string AUTHENTICATOR_TIMEOUT = "authenticatorTimeoutMinutes";		// The key for the authentication timeout in minutes;
		public const string SYSTEM_MODEL_PATH = "system.xml";							// Name of the file that will always be loaded into the model.
		public const string AUTHENTICATOR_STORES_NODE_NAME = "authenticationstores";	// Name of the MaverickLite node that contains the information to set up the authentication stores.
		public const string OUTPUT_FORMATTING = "outputFormatting"; 
		
		public const string PAGE_PATH_KEY = "${pagepath}";								// The name of a special parameter key added to the model providing a secure on unsecure path depending on the request.
		public const string SECURE_PAGE_PATH = "${securepath}";
		public const string WEB_APP_PATH = "${webapppath}"; 

		private static StringDictionary m_appConfigSettings;							// Contains application configuration key, value pairs

		public static ILog logger = log4net.LogManager.GetLogger(APP_LOGGING_ID);			// Used to provide logging functionality for the application.
		public static ILog VisitorLog = log4net.LogManager.GetLogger(APP_VISITORLOG_ID);	// Used to log IP addresses of each connecting client.

		//public static readonly string VisitorLogFilePath;			// File path to store a visitor log.	
		public static readonly string MaverickConfigFile;			// Physical path to the MaverickLite configuration file.
		//public static readonly string SecurityDirectoryPath;		// Physical path to a file holding authentication/authorisation information for security related classes.
		public static readonly string PhysicalApplicationPath;		// The physical path of the web application root directory.
		//public static readonly string WebDBConnectionString;		// The database connection string for the web server.
		public static readonly string VirtualDir;					// The virtual directory of the application, if it exists. This needs to be removed from incoming commands to allow exact regex pattern macthing.
		public static readonly bool IsDbMSSQL = false;				// Is the database in the above connection MSSQL?
		public static readonly bool IsDbPostgresql = false;			// Is the database in the above connection Postgresql?
		public static readonly string SubscriptionNotificationAddress = null;	// The email address to send notifications to for new subscriptions.
		public static readonly string ReloadCommand = null;			// The name of the page request that will cause the reloading of all the commnand objects.
		public static readonly string AuthenticatorDBConnString = null;	// The database connection string for the db authenticator. 
		public static readonly StorageTypes AuthenticatorDBType;
		public static readonly string AuthenticationDirectoryPath;	// The directory path for file based authentictaion.
		public static readonly string AuthenticationTimeoutMinutes;
		public static readonly XmlDocument SystemSettingsDom = new XmlDocument();	// XML DOM that loads common system settings into all controller models.
		public static readonly Hashtable AuthenticationStores = new Hashtable();	// [<store id>,<store XML node>] Using classes will retrieve the srore settings from here when an authentication operation is required.
		public static readonly Hashtable AuthenticationStoreTypes = new Hashtable();// [<store id>,<AuthenticatorTypesEnum>] Allows using classes to quickly identify the type of the authentication store.
		public static readonly string DefaultAuthenticationId = null;				// The default authentication id used if one is not specified for a command.
		public static readonly Formatting OutputFormatting = Formatting.Indented;	// We will indent the response output by default.

		public static Hashtable SpecialParameters = new Hashtable();	// Special parameter strings in the model, e.g. ${path}.

		static MavAppState()
		{
			// Initialise logging functionality.
			log4net.Config.XmlConfigurator.Configure();
			logger.Debug("MaverickLite log file initialised.");
			
			PhysicalApplicationPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

			// Read settings for this application from configuration file into static class field.
			XmlNode appConfigNode = (XmlNode)ConfigurationManager.GetSection(APP_CONFIG_SECTION);

			// Add a key that will hold an SSL dependent path in the special parameters.
			SpecialParameters.Add(PAGE_PATH_KEY, null);
			
			if(appConfigNode != null)
			{
				//logger.Debug("Application specific configuration node: " + appConfigNode.OuterXml);
				
				// Initialise the string dictionary to hold the application settings.
				m_appConfigSettings = new StringDictionary();
				
				foreach(XmlNode configNode in appConfigNode)
				{
					try
					{
						if(configNode.Name == AUTHENTICATOR_STORES_NODE_NAME)
						{
							foreach(XmlNode authNode in configNode.ChildNodes)
							{
								logger.Debug("Loading AuthenticationStore:");
								logger.Debug(authNode.OuterXml);

								try
								{
									string authId = authNode.Attributes.GetNamedItem("id").Value;
									string authType = authNode.Attributes.GetNamedItem("type").Value;
									string isDefault = (authNode.Attributes.GetNamedItem("default") != null) ? authNode.Attributes.GetNamedItem("default").Value : null;

									logger.Debug("authid=" + authId + ", authType=" + authType + ", isdefault=" + isDefault + ".");

									string authSettings = authNode.OuterXml;

									if(authId != null && authId.Trim().Length > 0)
									{
										logger.Debug("Loading authentication store settings for " + authId + ".");
										AuthenticationStores.Add(authId, authSettings);
										AuthenticationStoreTypes.Add(authId, AuthenticatorTypes.GetAuthenticatorType(authType));
									}

									if(DefaultAuthenticationId == null || (isDefault != null && Regex.Match(isDefault, "true", RegexOptions.IgnoreCase).Success))
									{
										DefaultAuthenticationId = authId;
										logger.Debug("default authid set to " + authId + ".");
									}
								}
								catch(Exception loadAuthExcp)
								{
									logger.Error("Exception loading Authenticator. " + loadAuthExcp.Message);
									logger.Error(authNode.OuterXml);
								}
							}
						}
						else
						{
							string settingKey = configNode.Attributes.GetNamedItem(APP_CONFIG_KEY_NAME).Value;
							string settingValue = configNode.Attributes.GetNamedItem(APP_CONFIG_VALUE_NAME).Value;

							if(Regex.Match(settingKey, @"^\$").Success)
							{
								SpecialParameters.Add(settingKey, settingValue);
								//logger.Debug("Application special parameter key added: key = " + settingKey + ", value = " + settingValue + ".");
							}
							else
							{
								m_appConfigSettings.Add(settingKey, settingValue);
								//logger.Debug("Application configuration setting added: key = " + settingKey + ", value = " + settingValue + ".");
							}
						}
					}
					catch //(Exception appSettingExcp)
					{
						//logger.Warn("Exception adding an application configuration setting. The XML node was: " + configNode.OuterXml + ". " + appSettingExcp.Message);
					}
				}
			}
			else
			{
				logger.Error(APP_CONFIG_SECTION + " section is undefined in the application configuration file.");
			}

			// Set the application properties.
			MaverickConfigFile = GetPath(GetConfigSetting(INITPARAM_CONFIG_FILE));
			if(MaverickConfigFile == null || MaverickConfigFile.Trim() == String.Empty)
			{
				MaverickConfigFile = DEFAULT_CONFIG_FILE;
			}

			//VisitorLogFilePath = GetConfigSetting(VISITOR_LOG_FILE);

			//SecurityDirectoryPath = GetPath(GetConfigSetting(DIRECTORY_PATH_KEY));

			//WebDBConnectionString = GetConfigSetting(WEB_DBCONNSTRING_KEY);

			VirtualDir = GetConfigSetting(APP_VIRTUALDIR_KEY);
			
			SubscriptionNotificationAddress = GetConfigSetting(SUBSCRIPTION_NOTIFICATIONADDRESS_KEY);

			ReloadCommand = GetConfigSetting(RELOAD_COMMAND_KEY);

			/*
			AuthenticatorDBConnString = GetConfigSetting(AUTHENTICATOR_DBCONN_STR);
			string webDbType = GetConfigSetting(AUTHENTICATOR_DB_TYPE);
			if(webDbType != null && Regex.Match(webDbType, AppState.DB_TYPE_POSTGRESQL, RegexOptions.IgnoreCase).Success)
			{
				AuthenticatorDBType = StorageTypes.Postgresql;
			}
			else	// Assume MSSQL.
			{
				AuthenticatorDBType = StorageTypes.MSSQL;
			}
			AuthenticationDirectoryPath = GetConfigSetting(AUTHENTICATOR_DIR_PATH);
			AuthenticationTimeoutMinutes = GetConfigSetting(AUTHENTICATOR_TIMEOUT);
			*/

			// Add the node containing the system wide configuration information.
			string systemDOMPath = PhysicalApplicationPath + SYSTEM_MODEL_PATH;
			try
			{							
				// Load in the DOM containing system wide settings.
				SystemSettingsDom.Load(systemDOMPath);

				logger.Debug("The system model was successfully loaded from " + systemDOMPath + ".");	
			}
			catch(Exception excp)
			{
				logger.Warn("Couldn't load the " + systemDOMPath + " document. " + excp.Message);
			}	

			string outputFormat = GetConfigSetting(OUTPUT_FORMATTING);
			if(outputFormat != null && outputFormat.ToLower() == "none")
			{
				OutputFormatting = Formatting.None;
			}
			//logger.Debug("MaverickConfigFile = " + MaverickConfigFile + ".");
		}


		/// <summary>
		/// Given a possible relative path attempts to determine the fully qualified physical path.
		/// </summary>
		/// <param name="path">Possible relative path.</param>
		/// <returns>The fully qualified physical path.</returns>
		public static string GetPath(string path)
		{
			if(path == null || path.Trim() == String.Empty)
			{
				return null;
			}

			// Determine the format of the path and construct the fully qualified physical path accordingly.
			if(!Regex.Match(path, @"^\\\\|^\w:|http:\/\/").Success)
			{
				return PhysicalApplicationPath + path;
			}
			else	// Fully qualified url, local or UNC path.
			{
				return path;
			}
		}


		/// <summary>
		/// Wrapper around the object holding the application configuration settings extracted
		/// from the App.Config file.
		/// </summary>
		/// <param name="key">The name of the configuration setting wanted.</param>
		/// <returns>The value of the configuration setting.</returns>
		public static string GetConfigSetting(string key)
		{
			if(m_appConfigSettings != null && m_appConfigSettings.ContainsKey(key))
			{
				return m_appConfigSettings[key];
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Takes a possible relative path and attempts to determine the fully qualified path.
		/// </summary>
		/// <param name="path">Possible relative path.</param>
		/// <param name="webApplicationPath">Physical path of web application.</param>
		/// <returns>Fully qualified path.</returns>
		public static string GetResourcePath(string path, string webApplicationPath)
		{
			string returnPath = path;

			if(path != null && Regex.Match(path, @"^(?!(\w:|\\\\|http:\/\/)).*(\\|\/).*").Success)
			{
				returnPath = webApplicationPath + path;
			}

			return returnPath;
		}

	}

	/// <summary>
	/// Handler for processing the App.Config file and passing the MaverickLite application specific 
	/// section to MavAppState.
	/// </summary>
	public class ConfigurationSectionHandler : IConfigurationSectionHandler
	{
		public object Create(object parent, object context, XmlNode configSection)
		{
			return configSection;
		}
	}
}
